package com.base.commons.tools.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.base.commons.tools.filter.CreateOrderLogFilter;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.util.GlobalTracer;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AsyncHttpClients {
    private static final Logger logger = LoggerFactory.getLogger(AsyncHttpClients.class);

    private static final Integer CONNECTION_TIMEOUT = 100 * 1000; //设置请求超时10秒钟 根据业务调整
    private static final Integer SO_TIMEOUT = 120 * 1000; //设置等待数据超时时间60秒钟 根据业务调整

    private CloseableHttpAsyncClient client;

    public AsyncHttpClients() {
        int connectTimeout = CONNECTION_TIMEOUT;
        IOReactorConfig config = IOReactorConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setSoTimeout(SO_TIMEOUT)
                .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2)
                .build();
        TrustManager[] trustAllCerts = new TrustManager[]{new RelaxTrustManager()};
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, null);
            client = HttpAsyncClientBuilder.create()
                    .setMaxConnTotal(1000)
                    .setDefaultIOReactorConfig(config)
                    .setSSLContext(sslContext)
                    .setSSLHostnameVerifier(new HostnameVerifier(){

						@Override
						public boolean verify(String paramString,
								SSLSession paramSslSession) {
							return true;
						}})
                    .setMaxConnPerRoute(1000)
                    .build();
            client.start();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            throw new RuntimeException("start up CloseableHttpAsyncClient error", e);
        }
    }

    public void destroy() throws IOException {
        client.close();
    }

    public Future<HttpResponse> formPost(String url, Map<String, Object> headers, Map<String, Object> params, FutureCallback<HttpResponse> callback) {
        List<BasicNameValuePair> formPairs = createFormPairs(params);
        HttpPost httpPost = createPost(url, formPairs);
        addHeaders(httpPost, headers);
        logger.info("=========> form post: url={} params={}", url, formPairs);
        return client.execute(httpPost, callback);
    }

    /**
     * 表单提交 POST
     *
     * @param url
     * @param params
     * @param callback
     * @return
     */
    public Future<HttpResponse> formPost(String url, Map<String, Object> params, FutureCallback<HttpResponse> callback) {
        return formPost(url, null, params, callback);
    }

    private HttpPost createPost(String url, List<BasicNameValuePair> formPairs) {
        HttpPost httpPost = new HttpPost(url);
        if (!formPairs.isEmpty()) {
            UrlEncodedFormEntity formEntity;
            try {
                formEntity = new UrlEncodedFormEntity(formPairs, "utf-8");
            } catch (UnsupportedEncodingException e) {
                logger.error("form request error:{}", e.getMessage());
                throw new RuntimeException("form request error", e);
            }
            httpPost.setEntity(formEntity);
        }
        return httpPost;
    }

    private List<BasicNameValuePair> createFormPairs(Map<String, Object> params) {
        List<BasicNameValuePair> formPairs = new ArrayList<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            BasicNameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue().toString());
            formPairs.add(pair);
        }
        return formPairs;
    }

    /**
     * GET请求
     *
     * @param url
     * @param callback
     * @return
     */
    public Future<HttpResponse> getRequest(String url, FutureCallback<HttpResponse> callback) {
        HttpGet httpGet = new HttpGet(url);
        logger.info("=========> get request: url={} ", url);
        return client.execute(httpGet, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param params
     * @param future
     * @return
     */
    public Future<HttpResponse> restPost(String url, String params, CompletableFuture future) {

        Map<String, Object> headers = new HashMap<>(1);
        headers.put("Content-Type", "application/json;charset=utf-8");
        FbgFutureCallback<HttpResponse> callback = new FbgFutureCallback<>(this,future,url);
        return rawPost(url, headers, params, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param params
     * @param callback
     * @return
     */
    public Future<HttpResponse> restPost(String url, String params, FbgFutureCallback<HttpResponse> callback) {

        Map<String, Object> headers = new HashMap<>(1);
        headers.put("Content-Type", "application/json;charset=utf-8");
        return rawPost(url, headers, params, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param headers
     * @param jsonBody
     * @param callback
     * @return
     */
    public Future<HttpResponse> rawPost(String url, Map<String, Object> headers, String jsonBody, FbgFutureCallback<HttpResponse> callback) {
        HttpPost httpPost = null;
        Tracer tracer = GlobalTracer.get();
        // 创建span
        Span span = tracer.buildSpan("HTTP POST:"+url).start();
        try (Scope ignored = tracer.activateSpan(span)) {
            tracer.activeSpan();
            callback.setSpan(span);
            //业务代码
            httpPost = new HttpPost(url);
            addHeaders(httpPost, headers);
            httpPost.setEntity(new StringEntity(jsonBody, "utf-8"));
            String contentType = "";
            Header contentTypeHeader = httpPost.getFirstHeader("Content-Type");
            if (contentTypeHeader != null) {
                contentType = contentTypeHeader.getValue();
            }
            JSONObject logObj = JSONObject.parseObject(jsonBody);
            String logStr = JSON.toJSONString(logObj,new CreateOrderLogFilter());
            logger.info("=========> raw post: Content-Type={} url={} params={}", contentType, url, logStr);
        } catch (Exception e) {
            TracingHelper.onError(e, span);
        }

        return client.execute(httpPost, callback);
    }

    private void addHeaders(HttpPost httpPost, Map<String, Object> headers) {
        if (headers != null && !headers.isEmpty()) {
            Set<Map.Entry<String, Object>> entrySet = headers.entrySet();
            for (Map.Entry<String, Object> entry : entrySet) {
                String key = entry.getKey();
                String value = entry.getValue().toString();
                httpPost.addHeader(key, value);
            }
        }

        try {
            Tracer tracer = GlobalTracer.get();
            Span span = tracer.activeSpan();
            tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMap() {
                @Override
                public void put(String key, String value) {
                    httpPost.setHeader(key, value);
                }

                @Override
                public Iterator<Map.Entry<String, String>> iterator() {
                    throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
                }
            });
        }catch (Exception e){
            logger.debug("请求第三方接口，链路跟踪透传headers失败,url:{}",httpPost.getURI().toString(),e);
        }
    }

    /**
     * json post请求
     *
     * @param url
     * @param
     * @param params
     * @param future
     * @return
     */
    public Future<HttpResponse> restPost(String url, Map<String, Object> headers, Object params, CompletableFuture future) {
        String jsonString = JSON.toJSONString(params);
        FbgFutureCallback<HttpResponse> callback = new FbgFutureCallback<>(this,future,url);
        return rawPost(url, headers, null, jsonString, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param
     * @param params
     * @param future
     * @return
     */
    public Future<HttpResponse> restPost(String url, Map<String, Object> headers, Object params, List<String> headerKeys,CompletableFuture future) {
        String jsonString = JSON.toJSONString(params);
        FbgFutureCallback<HttpResponse> callback = new FbgFutureCallback<>(this,future,url,headerKeys);
        return rawPost(url, headers, null, jsonString, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param
     * @param params
     * @param callback
     * @return
     */
    public Future<HttpResponse> restPost(String url, Map<String, Object> headers, Object params, FbgFutureCallback<HttpResponse> callback) {
        String jsonString = JSON.toJSONString(params);
        return rawPost(url, headers, null, jsonString, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param urlSpliceParam
     * @param params
     * @param callback
     * @return
     */
    public Future<HttpResponse> restNewPost(String url, Map<String, Object> urlSpliceParam, Object params, FbgFutureCallback<HttpResponse> callback) {
        String jsonString = JSON.toJSONString(params);
        Map<String, Object> headers = new HashMap<>(1);
        headers.put("Content-Type", "application/json;charset=utf-8");
        return rawPost(url, headers, urlSpliceParam, jsonString, callback);
    }

    /**
     * json post请求
     *
     * @param url
     * @param headers
     * @param jsonBody
     * @param callback
     * @return
     */
    public Future<HttpResponse> rawPost(String url, Map<String, Object> headers, Map<String, Object> urlSpliceParam, String jsonBody, FbgFutureCallback<HttpResponse> callback) {
        HttpPost httpPost = null;
        Tracer tracer = GlobalTracer.get();
        // 创建span
        Span span = tracer.buildSpan("HTTP POST:"+url).start();
        try (Scope ignored = tracer.activateSpan(span)) {
            tracer.activeSpan();
            callback.setSpan(span);
            //业务代码
            url = urlSplice(url, urlSpliceParam);
            httpPost = new HttpPost(url);
            addHeaders(httpPost, headers);
            httpPost.setEntity(new StringEntity(jsonBody, "utf-8"));
            String contentType = "";
            Header contentTypeHeader = httpPost.getFirstHeader("Content-Type");
            if (contentTypeHeader != null) {
                contentType = contentTypeHeader.getValue();
            }
            JSONObject logObj = JSON.parseObject(jsonBody);
            String logStr = JSON.toJSONString(logObj,new CreateOrderLogFilter());
            logger.info("=========> raw post: Content-Type={} url={} params={}", contentType, url, logStr);
        } catch (Exception e) {
            TracingHelper.onError(e, span);
        }

        return client.execute(httpPost, callback);
    }

    /**
     * url拼接参数
     * @param url
     * @param params
     * @return
     */
    public String urlSplice(String url, Map<String, Object> params){
        if (params != null && !params.isEmpty()) {
            StringBuilder builder = new StringBuilder(url);
            builder.append("?");
            params.forEach((k, v) -> builder.append(k).append("=").append(v).append("&"));
            builder.deleteCharAt(builder.length() - 1);
            url = builder.toString();
        }
        return url;
    }

    public JSONObject getResponseHeaders(HttpResponse response,List<String> headerKeys) {
        JSONObject json = new JSONObject();
        if(!CollectionUtils.isEmpty(headerKeys)){
            headerKeys.stream().forEach(headerKey ->{
                Header header = response.getFirstHeader(headerKey);
                if(header!=null){
                    json.put(headerKey,header.getValue());
                }
            });
        }

        return json;
    }

    public String responseForPlain(String charset, HttpResponse response) {
        String content;
        try {
            content = EntityUtils.toString(response.getEntity(), charset);
        } catch (IOException e) {
            throw new RuntimeException("read response result error", e);
        }
        return content;
    }

    public String responseForPlain(String charset, Long reqStartTime,HttpResponse response) {
        String content;
        try {
            content = EntityUtils.toString(response.getEntity(), charset);
        } catch (IOException e) {
            throw new RuntimeException("read response result error", e);
        }
        return content;
    }

    public String futureForPlain(Future<HttpResponse> future, String charset) {
        String content;
        try {
            HttpResponse response = future.get();
            content = responseForPlain(charset, response);
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException("read future result error", e);
        }
        return content;
    }

    public JSONObject responseForJson(HttpResponse response) {
        String plain = responseForPlain("utf-8", response);
        return JSON.parseObject(plain, Feature.OrderedField);
    }

    public JSONObject responseForJson(HttpResponse response,Long startTime) {
        String plain = responseForPlain("utf-8",startTime,response);
        return JSON.parseObject(plain, Feature.OrderedField);
    }

    public JSONObject futureForJson(Future<HttpResponse> future) {
        return JSON.parseObject(futureForPlain(future, "utf-8"), Feature.OrderedField);
    }

    public <T> T futureForObject(Future<HttpResponse> future, Class<T> clazz) {
        JSONObject json = futureForJson(future);
        return JSON.toJavaObject(json, clazz);
    }

    class RelaxTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(X509Certificate[] certs) {
            return true;
        }

    }

}
